﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Reflection;
using FiveMonth.FormWork;
using NUnit.Framework;
using MSWord = Microsoft.Office.Interop.Word;

namespace Tester
{
    public class TestWord
    {
        private object missing = Missing.Value;

        [Test]
        public void Test1()
        {
            var wdHelp = new WordHelper();
            wdHelp.CreateOneDocument("D:\\Documents\\成绩表.dotx", missing, missing, missing);

            var data = Mock.GetScore();

            //2 调用模板替换函数，生成实际的数据
            SetNameCellValue(wdHelp, data);


            wdHelp.SaveAs("D:\\Documents\\成绩表.doc");
            wdHelp.Close();

            Console.WriteLine("ok");
        }

        public void SetNameCellValue<T>(WordHelper wdHelp, T data) where T : class
        {

            //(一) 这里设置表头的项目，比如报表日期
            //特别注意：为了容易起见，命名单元格的规则如下，注意：word中书签不能以下划线开头，可能是保留的书签使用
            //1.1 开头处的命名单元格，以1个下划线结尾，比如，报告日期_
            //1.2 中间循环命名单元格，就是正常的，与数据集的字段名一致为好
            //1.3 结尾处的命名单元格，以2个下划线结尾，比如，合计__
            try
            {//为了程序自动处理方便起见
                wdHelp.GoToBookMark("title");//开始处的命名单元格，都以1个下划线_结尾
                wdHelp.InsertText(GetValue("title", data));
                wdHelp.GoToBookMark("author");//开始处的命名单元格，都以1个下划线_结尾
                wdHelp.InsertText(GetValue("author", data));
                wdHelp.GoToBookMark("createdate");//开始处的命名单元格，都以1个下划线_结尾
                wdHelp.InsertText(GetValue("createdate", data, "yyyy年MM月dd日"));
            }
            catch (System.Exception ex)
            {

            }

            //(二) 根据数据源的个数，设置重复变化的数据行组，
            //1 声明与命名单元格相关的变量和数组
            int min_Row = 65536, min_Col = 65536, max_Row = 0;
            string min_Name = "";
            int nameCellCount = wdHelp.wordDoc.Bookmarks.Count;//获得命名单元格的总数
            int[] nameCellRow = new int[nameCellCount];//某个命名单元格的行
            int[] nameCellColumn = new int[nameCellCount];//某个命名单元格的列
            //string[] nameCellTag = new string[nameCellCount];//某个命名单元格的常规地址 ，比如 $A$1
            string[] nameCellName = new string[nameCellCount];//某个命名单元格的自定义名称，比如 工资

            string strName, str;
            int row1, row2;//选择重复的行
            int nameCellIdx = 0;
            MSWord.Range tmpRange, srcRange, desRange;

            //2 寻找所有命名的单元格，并找到行号最小者，以便在它之前插入循环行
            for (int k = 0; k < nameCellCount; k++)
            {
                strName = wdHelp.wordDoc.Bookmarks[k + 1].Name;//
                if (strName.EndsWith("_"))
                {
                    continue;//如果第一个字符为下划线，则认为是固定命名单元格，不是命名的循环单元格
                }
                try
                {
                    tmpRange = wdHelp.wordDoc.Bookmarks[strName].Range;
                    var tables = tmpRange.Tables;
                    var table = tables[1];
                    var cell = table.Cell(0,0);
                    //var cell = cells[1];
                    nameCellColumn[nameCellIdx] = cell.ColumnIndex;// app.ActiveCell.Column;
                    nameCellRow[nameCellIdx] = cell.RowIndex;//app.ActiveCell.Row;
                    nameCellName[nameCellIdx] = strName;

                    //nameCellTag[nameCellIdx] = "";// app.ActiveCell.Address;//$A$1
                    //nameCellTag[nameCellIdx] = "";// nameCellTag[nameCellIdx].Split('$')[1];//$A$1--> A

                    if (min_Row > nameCellRow[nameCellIdx])
                    {
                        min_Row = nameCellRow[nameCellIdx];//最小的行号
                        min_Col = nameCellColumn[nameCellIdx];
                        min_Name = nameCellName[nameCellIdx];

                    }
                    if (max_Row < nameCellRow[nameCellIdx]) max_Row = nameCellRow[nameCellIdx]; ;//最大行号
                    nameCellIdx++;//真实的循环的命名单元格序号
                }
                catch (System.Exception ex)
                {

                }

            }
            nameCellCount = nameCellIdx;//实际要处理的循环的命名单元格数目
            int loopRow = max_Row - min_Row + 1;//一次循环的函数
            //3 也可以使用 //foreach ( Word.Bookmark bk in wh.wordDoc.Bookmarks)MessageBox.Show(bk.Name);
            MSWord.Table operTable;
            // 方法1 通常使用第一个表来作为模板表
            operTable = wdHelp.wordDoc.Tables[1];
            // 方法2 使用一个书签来标志模板表
            try
            {//使用一个特殊的标签 table_bookmark_template_
                tmpRange = wdHelp.wordDoc.Bookmarks.get_Item("报表模板__").Range;//
                operTable = tmpRange.Tables[1];//得到该书签所在的表，以它为报表的循环模板
            }
            catch (System.Exception ex)
            {

            }

            //
            int template_start = 0;
            int template_end = 0;
            int table_columns = operTable.Columns.Count;//本表格的烈数

            tmpRange = operTable.Cell(min_Row, 1).Range;
            template_start = tmpRange.Start;//循环行组的第一行，第一个单元格，到文档开头的距离
            tmpRange = operTable.Cell(max_Row, table_columns).Range;
            template_end = tmpRange.Start; //循环行组的最后行，最后一个单元格，到文档开头的距离

            srcRange = operTable.Cell(1, 1).Range;
            desRange = operTable.Cell(1, 1).Range;

            //4 根据数据集的实际数据行数，查找命名单元格，循环插入数据
            int cur_row = min_Row;//0;// min_Row;//
            for (int dt_row_idx = 0; dt_row_idx < 5; dt_row_idx++)
            {//循环实际的数据行数

                //goto xx1;//跳过方法1
                //方法1 整体多行组，复制和粘贴，看起来复杂
                //4.1 找到行号最小的循环行组的命名单元格，以它来定位
                tmpRange = wdHelp.wordDoc.Bookmarks.get_Item(min_Name).Range;//app.Goto(min_Name);
                                                                             //4.2 //插入循环重复的摸板行组的行，使得所有命名单元格都向后移，以便下次循环查找定位使用
                                                                             // for (int j = 0; j < loopRow; j++)
                                                                             // {//插入需要重复循环的行数loopRow的空行
                                                                             //     app.ActiveCell.EntireRow.Insert();
                                                                             // }

                //4.3 定位到摸板行组首行
                tmpRange = wdHelp.wordDoc.Bookmarks.get_Item(min_Name).Range;//转到摸板行组的行号最小的命名单元格，以它来定位
                row1 = tmpRange.Cells[1].RowIndex;//a  //摸板行组的第一行
                row2 = row1 + loopRow - 1; //摸板行组的最后一行

                template_start = operTable.Cell(row1, 1).Range.Start;
                //template_end = operTable.Cell(row2, table_columns).Range.Start;//这样少一列
                template_end = operTable.Cell(row2 + 1, 1).Range.Start;//到下一行，第一个单元格

                //4.4 复制整体模板的多行组，固定的摸板的格式和相关的文字说明，也可一个一个单元格复制
                srcRange.SetRange(template_start, template_end);//整体多行组复制摸板行组
                srcRange.Copy();

                //4.5 定位到新加入行的第一个单元格内
                //row1 = row1 - loopRow;//向上回退到新加入的行组
                //row2 = row2 - loopRow;

                //4.6 粘贴整体多行组，固定的摸板的格式和相关的文字说明
                desRange = operTable.Rows[row1].Range;//整体多行组粘贴摸板行组
                desRange.Paste();

                //xx1:

                //goto xx2;      //跳过方法2 
                //               //方法2  一行一行复制粘贴，代码很少
                //for (int j = 0; j < loopRow; j++)
                //{//插入需要重复循环的行数loopRow的空行，一行一行的复制粘贴
                //    //复制模板行
                //    srcRange = operTable.Rows[cur_row + j + j].Range;// 所以，粘贴几行，就要多加几行，j+j
                //    srcRange.Copy();
                //    //粘贴到目标行
                //    desRange = operTable.Rows[cur_row + j].Range;//因为，新粘贴的行 在原来模板行的上面
                //    desRange.Paste();
                //}
                //cur_row += loopRow;

                //xx2:


                //4.7 动态的数值加入
                for (int name_cell_idx = 0; name_cell_idx < nameCellCount; name_cell_idx++)
                {//循环命名单元格数量
                    //str = string.Format("{0}{1}", nameCellTag[name_cell_idx], nameCellRow[name_cell_idx]);
                    if (nameCellName[name_cell_idx].ToString() == "order_num")
                    {//序号
                        str = string.Format("{0}", dt_row_idx + 1);
                    }
                    else
                    {//以命名单元格的名称，来取数据表行的对应字段的值
                        str = dt_row_idx + "_" + name_cell_idx;

                    }
                    //   wdHelp.wordDoc.Tables[1].Cell(nameCellRow[name_cell_idx], nameCellColumn[name_cell_idx]).Range.Text = str;
                    operTable.Cell(nameCellRow[name_cell_idx], nameCellColumn[name_cell_idx]).Range.Text = str;
                    nameCellRow[name_cell_idx] += loopRow;//行号增加重复行的个数loopRow，准备下个循环，定位行使用
                }

            }
            // 5 删除重复的摸板行，不再需要

            //方法1 多行组一次删除
            tmpRange = wdHelp.wordDoc.Bookmarks.get_Item(min_Name).Range;//找到行号最小的命名单元格，以它来定位
            row1 = tmpRange.Cells[1].RowIndex;//app.ActiveCell.Row;
            row2 = row1 + loopRow - 1;//选择重复的行
            template_start = operTable.Cell(row1, 1).Range.Start;
            //template_end = operTable.Cell(row2, table_columns).Range.Start;//这样少一列
            template_end = operTable.Cell(row2 + 1, 1).Range.Start;//到下一行，第一个单元格
            tmpRange.SetRange(template_start, template_end);//整体多行组复制摸板行组
            tmpRange.Cut();



            //goto mm2;//跳过方法2

            ////方法2  一行一行地删除
            //tmpRange = wdHelp.wordDoc.Bookmarks.get_Item(min_Name).Range;//找到行号最小的命名单元格，以它来定位
            //row1 = tmpRange.Cells[1].RowIndex;//app.ActiveCell.Row;
            //row2 = row1 + loopRow - 1;//选择重复的行
            //for (int dd = 0; dd < loopRow; dd++)
            //{
            //    operTable.Rows[row1].Delete();//同样的行号，因为删除前面的，后面向前移动
            //}

            //mm2:

            //(三) 清除剪贴板，避免Excel关闭工作簿的时候出现提示
            //1 删除剪切板的内容，防止退出提示
            //app.CutCopyMode = flase;//Excel.XlCutCopyMode.xlCut;//删除剪切板的内容，防止退出提示
            //2 直接用range("A1").copy就行，不必把剪贴板都清空，这会影响其他进程的工作的
            //ws.Range[ws.Cells[1, 1], ws.Cells[1, 1]].Copy();
            //wdHelp.wordDoc.Tables[1].Cell(1, 1).Range.Copy();
            operTable.Cell(1, 1).Range.Copy();

            //(四) 结尾方面的工作
            try
            {//为了程序自动处理方便起见
                wdHelp.GoToBookMark("sign");//结尾处的命名单元格，都以2个下划线__结尾
                wdHelp.InsertText(GetValue("sign", data, "yyyy-MM-dd hh:mm:ss"));
            }
            catch (System.Exception ex)
            {

            }

        }

        private string GetValue(string markName, object data, string fmt = null)
        {
            object obj = null;
            var membername = markName.Trim('_');
            var type = data.GetType();
            var members = type.GetMembers(BindingFlags.Public | BindingFlags.GetField | BindingFlags.GetProperty | BindingFlags.Instance);
            var member = members.FirstOrDefault(x => x.Name.Equals(membername, StringComparison.OrdinalIgnoreCase));
            if (member is PropertyInfo)
            {
                obj = ((PropertyInfo)member).GetValue(data);
            }
            else if (member is FieldInfo)
            {
                obj = ((FieldInfo)member).GetValue(data);
            }

            if (obj != null)
            {
                if (string.IsNullOrWhiteSpace(fmt))
                {
                    return obj.ToString();
                }
                else
                {
                    return string.Format("{0:" + fmt + "}", obj);
                }
            }
            return null;
        }
    }
}

